﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using CefSharp;

namespace pinduoduo.CefHandler
{
    public enum HttpMethod
    {
        None,
        GET,
        POST,
        CONNECT,
        OPTIONS,
    }
    public class FilterData : IDisposable
    {
        public FilterData(string name, string url) { Name = name; Url = url; }
        public FilterData(string name, string url, HttpMethod _Method) { Name = name; Url = url; Method = _Method; }
        /// <summary>
        /// 别名
        /// </summary>
        public string Name { get; set; }
        public string Url { get; set; }
        public HttpMethod Method { get; set; } = HttpMethod.GET;
        public bool HasData { get { return Filter?.Data?.Length > 0; } }
        public UInt64 Identifier { get; set; }
        public MyResponseFilter Filter { get; set; }
        public void AddFilter(UInt64 _Identifier, MyResponseFilter _Filter)
        {
            Filter?.Dispose(true);
            Identifier = _Identifier;
            Filter = _Filter;
        }

        bool Disposed = false;
        public void Dispose()
        {
            if (Disposed)
                return;
            Filter?.Dispose(true);
            Disposed = true;
        }
        public override string ToString()
        {
            return $"Name={Name},DataStr={Filter?.DataStr}";
        }
    }

    public class MyRequestHandler : IRequestHandler, IDisposable
    {
        public MyRequestHandler(string name, string _url, HttpMethod _Method = HttpMethod.GET, bool _filter = false)
        {
            Filters.Add(new FilterData(name, _url, _Method));
            filter = _filter;
        }
        public MyRequestHandler(Dictionary<string, string> _urls, HttpMethod _Method = HttpMethod.GET, bool _filter = false)
        {
            foreach (var u in _urls)
            {
                Filters.Add(new FilterData(u.Key, u.Value, _Method));
            }
            filter = _filter;
        }
        public MyRequestHandler(FilterData Filter, bool _filter = false)
        {
            Filters.Add(Filter);
            filter = _filter;
        }
        public MyRequestHandler(List<FilterData> _Filters, bool _filter = false)
        {
            Filters.AddRange(_Filters);
            filter = _filter;
        }

        bool Disposed = false;
        /// <summary>
        /// 是否过滤，如果不过滤就截获
        /// </summary>
        bool filter = false;
        /// <summary>
        /// 过滤、截获 url 列表 和 捕获过滤数据
        /// </summary>
       public List<FilterData> Filters = new List<FilterData>();
        MyResourceRequestHandler handler;

        /// <summary>
        /// 证书认证
        /// Called when the browser needs credentials from the user.
        /// </summary>
        /// <param name="chromiumWebBrowser">The ChromiumWebBrowser control.</param>
        /// <param name="browser">the browser object.</param>
        /// <param name="originUrl">is the origin making this authentication request.</param>
        /// <param name="isProxy">indicates whether the host is a proxy server.</param>
        /// <param name="host">hostname.</param>
        /// <param name="port">port number.</param>
        /// <param name="realm">realm.</param>
        /// <param name="scheme">scheme.</param>
        /// <param name="callback">Callback interface used for asynchronous continuation of authentication requests.</param>
        /// <returns>
        /// Return true to continue the request and call <see cref="IAuthCallback.Continue(string, string)"/> when the authentication
        /// information is available. Return false to cancel the request.
        /// </returns>
        bool IRequestHandler.GetAuthCredentials(IWebBrowser chromiumWebBrowser, IBrowser browser, string originUrl, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback)
        { //NOTE: We also suggest you explicitly Dispose of the callback as it wraps an unmanaged resource.

            //Example #1
            //Spawn a Task to execute our callback and return true;
            //Typical usage would see you invoke onto the UI thread to open a username/password dialog
            //Then execute the callback with the response username/password
            //You can cast the IWebBrowser param to ChromiumWebBrowser to easily access
            //control, from there you can invoke onto the UI thread, should be in an async fashion
            //Load https://httpbin.org/basic-auth/cefsharp/passwd in the browser to test

            //Task.Run(() =>
            //{
            //    using (callback)
            //        {
            //        if (originUrl.Contains("https://httpbin.org/basic-auth/"))
            //            {
            //            var parts = originUrl.Split('/');
            //            var username = parts[parts.Length - 2];
            //            var password = parts[parts.Length - 1];
            //            callback.Continue(username, password);
            //            }
            //        }
            //});

            return true;

            //Example #2
            //Return false to cancel the request
            //callback.Dispose();
            //return false;
        }

        /// <summary>
        /// Called on the CEF IO thread before a resource request is initiated.
        /// </summary>
        /// <param name="chromiumWebBrowser">the ChromiumWebBrowser control.</param>
        /// <param name="browser">represent the source browser of the request.</param>
        /// <param name="frame">represent the source frame of the request.</param>
        /// <param name="request">represents the request contents and cannot be modified in this callback.</param>
        /// <param name="isNavigation">will be true if the resource request is a navigation.</param>
        /// <param name="isDownload">will be true if the resource request is a download.</param>
        /// <param name="requestInitiator">is the origin (scheme + domain) of the page that initiated the request.</param>
        /// <param name="disableDefaultHandling">[in,out] to true to disable default handling of the request, in which case it will need
        /// to be handled via <see cref="IResourceRequestHandler.GetResourceHandler"/> or it will be canceled.</param>
        /// <returns>
        /// To allow the resource load to proceed with default handling return null. To specify a handler for the resource return a
        /// <see cref="IResourceRequestHandler"/> object. If this callback returns null the same method will be called on the associated
        /// <see cref="IRequestContextHandler"/>, if any.
        /// </returns>
        IResourceRequestHandler IRequestHandler.GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling)
        {
            if (Filters == null|| Filters.Count==0) 
            {
                return null;
            }
            var fd = Filters.FirstOrDefault(u => request.Url.Contains(u.Url) && (u.Method == HttpMethod.None || $"{u.Method}".ToLower() == request.Method.ToLower()));
            if (fd != null)
            {
                handler = new MyResourceRequestHandler(fd, filter);
                return handler;
            }
            return null;
        }

        /// <summary>
        /// Called before browser navigation. If the navigation is allowed <see cref="IWebBrowser.FrameLoadStart"/> and
        /// <see cref="IWebBrowser.FrameLoadEnd"/>
        /// will be called. If the navigation is canceled <see cref="IWebBrowser.LoadError"/> will be called with an ErrorCode value of
        /// <see cref="CefErrorCode.Aborted"/>.
        /// </summary>
        /// <param name="chromiumWebBrowser">the ChromiumWebBrowser control.</param>
        /// <param name="browser">the browser object.</param>
        /// <param name="frame">The frame the request is coming from.</param>
        /// <param name="request">the request object - cannot be modified in this callback.</param>
        /// <param name="userGesture">The value will be true if the browser navigated via explicit user gesture (e.g. clicking a link) or
        /// false if it navigated automatically (e.g. via the DomContentLoaded event).</param>
        /// <param name="isRedirect">has the request been redirected.</param>
        /// <returns>
        /// Return true to cancel the navigation or false to allow the navigation to proceed.
        /// </returns>
        bool IRequestHandler.OnBeforeBrowse(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect)
        {
            return false;
        }

        /// <summary>
        /// Called to handle requests for URLs with an invalid SSL certificate. Return true and call
        /// <see cref="IRequestCallback.Continue"/> either in this method or at a later time to continue or cancel the request.  
        /// If CefSettings.IgnoreCertificateErrors is set all invalid certificates will be accepted without calling this method.
        /// </summary>
        /// <param name="chromiumWebBrowser">the ChromiumWebBrowser control.</param>
        /// <param name="browser">the browser object.</param>
        /// <param name="errorCode">the error code for this invalid certificate.</param>
        /// <param name="requestUrl">the url of the request for the invalid certificate.</param>
        /// <param name="sslInfo">ssl certificate information.</param>
        /// <param name="callback">Callback interface used for asynchronous continuation of url requests. If empty the error cannot be
        /// recovered from and the request will be canceled automatically.</param>
        /// <returns>
        /// Return false to cancel the request immediately. Return true and use <see cref="IRequestCallback"/> to execute in an async
        /// fashion.
        /// </returns>
        bool IRequestHandler.OnCertificateError(IWebBrowser chromiumWebBrowser, IBrowser browser, CefErrorCode errorCode, string requestUrl, ISslInfo sslInfo, IRequestCallback callback)
        {
            //NOTE: We also suggest you wrap callback in a using statement or explicitly execute callback.Dispose as callback wraps an unmanaged resource.

            //Example #1
            //Return true and call IRequestCallback.Continue() at a later time to continue or cancel the request.
            //In this instance we'll use a Task, typically you'd invoke a call to the UI Thread and display a Dialog to the user
            //You can cast the IWebBrowser param to ChromiumWebBrowser to easily access
            //control, from there you can invoke onto the UI thread, should be in an async fashion

            //Task.Run(() =>
            //{
            //    //NOTE: When executing the callback in an async fashion need to check to see if it's disposed
            //    if (!callback.IsDisposed)
            //        {
            //        using (callback)
            //            {
            //            //We'll allow the expired certificate from badssl.com
            //            if (requestUrl.ToLower().Contains("https://expired.badssl.com/"))
            //                {
            //                callback.Continue(true);
            //                }
            //            else
            //                {
            //                callback.Continue(false);
            //                }
            //            }
            //        }
            //});

            return true;

            //Example #2
            //Execute the callback and return true to immediately allow the invalid certificate
            //callback.Continue(true); //Callback will Dispose it's self once exeucted
            //return true;

            //Example #3
            //Return false for the default behaviour (cancel request immediately)
            //callback.Dispose(); //Dispose of callback
            //return false;
        }

        /// <summary>
        /// Called on the CEF UI thread when the window.document object of the main frame has been created.
        /// </summary>
        /// <param name="chromiumWebBrowser">the ChromiumWebBrowser control</param>
        /// <param name="browser">the browser object</param>
        void IRequestHandler.OnDocumentAvailableInMainFrame(IWebBrowser chromiumWebBrowser, IBrowser browser)
        {
        }

        /// <summary>
        /// Called on the UI thread before OnBeforeBrowse in certain limited cases where navigating a new or different browser might be
        /// desirable. This includes user-initiated navigation that might open in a special way (e.g. links clicked via middle-click or
        /// ctrl + left-click) and certain types of cross-origin navigation initiated from the renderer process (e.g. navigating the top-
        /// level frame to/from a file URL).
        /// </summary>
        /// <param name="chromiumWebBrowser">the ChromiumWebBrowser control.</param>
        /// <param name="browser">the browser object.</param>
        /// <param name="frame">The frame object.</param>
        /// <param name="targetUrl">target url.</param>
        /// <param name="targetDisposition">The value indicates where the user intended to navigate the browser based on standard
        /// Chromium behaviors (e.g. current tab, new tab, etc).</param>
        /// <param name="userGesture">The value will be true if the browser navigated via explicit user gesture (e.g. clicking a link) or
        /// false if it navigated automatically (e.g. via the DomContentLoaded event).</param>
        /// <returns>
        /// Return true to cancel the navigation or false to allow the navigation to proceed in the source browser's top-level frame.
        /// </returns>
        bool IRequestHandler.OnOpenUrlFromTab(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture)
        {
            return false;
        }

        /// <summary>
        /// Called when a plugin has crashed.
        /// </summary>
        /// <param name="chromiumWebBrowser">the ChromiumWebBrowser control.</param>
        /// <param name="browser">the browser object.</param>
        /// <param name="pluginPath">path of the plugin that crashed.</param>
        void IRequestHandler.OnPluginCrashed(IWebBrowser chromiumWebBrowser, IBrowser browser, string pluginPath)
        {
            // TODO: Add your own code here for handling scenarios where a plugin crashed, for one reason or another.
        }

        /// <summary>
        /// Called when JavaScript requests a specific storage quota size via the webkitStorageInfo.requestQuota function. For async
        /// processing return true and execute <see cref="IRequestCallback.Continue"/> at a later time to grant or deny the request or
        /// <see cref="IRequestCallback.Cancel"/> to cancel.
        /// </summary>
        /// <param name="chromiumWebBrowser">The ChromiumWebBrowser control.</param>
        /// <param name="browser">the browser object.</param>
        /// <param name="originUrl">the origin of the page making the request.</param>
        /// <param name="newSize">is the requested quota size in bytes.</param>
        /// <param name="callback">Callback interface used for asynchronous continuation of url requests.</param>
        /// <returns>
        /// Return false to cancel the request immediately. Return true to continue the request and call
        /// <see cref="IRequestCallback.Continue"/> either in this method or at a later time to grant or deny the request.
        /// </returns>
        bool IRequestHandler.OnQuotaRequest(IWebBrowser chromiumWebBrowser, IBrowser browser, string originUrl, long newSize, IRequestCallback callback)
        {
            //NOTE: If you do not wish to implement this method returning false is the default behaviour
            // We also suggest you explicitly Dispose of the callback as it wraps an unmanaged resource.
            //callback.Dispose();
            //return false;

            //NOTE: When executing the callback in an async fashion need to check to see if it's disposed
            //if (!callback.IsDisposed)
            //    {
            //    using (callback)
            //        {
            //        //Accept Request to raise Quota
            //        //callback.Continue(true);
            //        //return true;
            //        }
            //    }

            return false;
        }

        /// <summary>
        /// Called when the render process terminates unexpectedly.
        /// </summary>
        /// <param name="chromiumWebBrowser">The ChromiumWebBrowser control.</param>
        /// <param name="browser">the browser object.</param>
        /// <param name="status">indicates how the process terminated.</param>
        void IRequestHandler.OnRenderProcessTerminated(IWebBrowser chromiumWebBrowser, IBrowser browser, CefTerminationStatus status)
        {
            // TODO: Add your own code here for handling scenarios where the Render Process terminated for one reason or another.
            //chromiumWebBrowser.Load(CefExample.RenderProcessCrashedUrl);
        }

        /// <summary>
        /// Called on the CEF UI thread when the render view associated with browser is ready to receive/handle IPC messages in the
        /// render process.
        /// </summary>
        /// <param name="chromiumWebBrowser">The ChromiumWebBrowser control.</param>
        /// <param name="browser">the browser object.</param>
        void IRequestHandler.OnRenderViewReady(IWebBrowser chromiumWebBrowser, IBrowser browser)
        {
        }

        /// <summary>
        /// Called when the browser needs user to select Client Certificate for authentication requests (eg. PKI authentication).
        /// </summary>
        /// <param name="chromiumWebBrowser">The ChromiumWebBrowser control.</param>
        /// <param name="browser">the browser object.</param>
        /// <param name="isProxy">indicates whether the host is a proxy server.</param>
        /// <param name="host">hostname.</param>
        /// <param name="port">port number.</param>
        /// <param name="certificates">List of Client certificates for selection.</param>
        /// <param name="callback">Callback interface used for asynchronous continuation of client certificate selection for
        /// authentication requests.</param>
        /// <returns>
        /// Return true to continue the request and call ISelectClientCertificateCallback.Select() with the selected certificate for
        /// authentication. Return false to use the default behavior where the browser selects the first certificate from the list.
        /// 
        /// </returns>
        //bool IRequestHandler.OnSelectClientCertificate(IWebBrowser chromiumWebBrowser, IBrowser browser, bool isProxy, string host, int port, X509Certificate2Collection certificates, ISelectClientCertificateCallback callback)
        //{
        //    //callback.Dispose();
        //    return false;
        //}

        public override string ToString()
        {
            if (!Filters?.Any() ?? true)
                return string.Empty;
            return string.Join("\r\n", Filters.Select(f => f.ToString()));
        }
        public void Dispose()
        {
            if (Disposed)
                return;
            handler?.Dispose();
            Filters?.ForEach(f => f.Dispose());
            Filters?.Clear();
            Filters = null;
            Disposed = true;
        }


        //bool IRequestHandler.OnSelectClientCertificate(IWebBrowser chromiumWebBrowser, IBrowser browser, bool isProxy, string host, int port, X509Certificate2Collection certificates, ISelectClientCertificateCallback callback)
        //{
        //    //callback.Dispose();
        //    return false;
        //}

        public bool OnSelectClientCertificate(IWebBrowser chromiumWebBrowser, IBrowser browser, bool isProxy, string host, int port, X509Certificate2Collection certificates, ISelectClientCertificateCallback callback)
        {
            return false;
        }
    }
}
